home *** CD-ROM | disk | FTP | other *** search
Wrap
#!/usr/bin/python # Download a Launchpad bug report, get its source package, check if it has # apport hooks, and if so, run and upload them. # # Copyright (c) 2009 Canonical Ltd. # Author: Martin Pitt <martin.pitt@ubuntu.com> # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. import sys, os.path, optparse, tempfile, re, email from glob import glob import apport from launchpadlib.errors import HTTPError from apport.crashdb_impl.launchpad import CrashDatabase bug_target_re = re.compile( r'/ubuntu/(?:(?P<suite>[^/]+)/)?\+source/(?P<source>[^/]+)$') def upload(report, bug): '''Upload collected information to Launchpad bug.''' print 'Uploading additional information to Launchpad bug...' # we want to reuse the knowledge of write_mime() with all its different input # types and output formatting; however, we have to dissect the mime ourselves, # since we can't just upload it as a blob mime = tempfile.TemporaryFile() report.write_mime(mime) mime.flush() mime.seek(0) msg = email.message_from_file(mime) msg_iter = msg.walk() # first part is the multipart container part = msg_iter.next() assert part.is_multipart() # second part should be an inline text/plain attachments with all short # fields part = msg_iter.next() assert not part.is_multipart() assert part.get_content_type() == 'text/plain' print' short text data...' bug.newMessage(content=part.get_payload(decode=True), subject='apport-collect data') # other parts are the attachments: for part in msg_iter: print ' attachment: %s...' % part.get_filename() bug.addAttachment(comment='', description=part.get_filename(), content_type=None, data=part.get_payload(decode=True), filename=part.get_filename(), is_patch=False) # change bug status from "Incomplete" for task in bug.bug_tasks: if task.status == 'Incomplete': task.transitionToStatus(status='New') def collect(report, package): '''Collect information for given package.''' print 'Collecting apport information for source package %s...' % package try: report.add_package_info(package) except ValueError: # this happens for source package tasks which do not have an identical # binary package name pass report.add_hooks_info(None) # # main # optparser = optparse.OptionParser('%prog [options] <Launchpad bug number>') optparser.add_option('-p', '--package', help="Collect information for this package. If not given, it will be inferred from the bug report's source package tasks.", type='string', dest='package') (opts, args) = optparser.parse_args() if len(args) != 1: optparser.error('incorrect number of arguments; use --help for a short online help') sys.exit(1) try: bug_number = int(args[0]) except ValueError: optparser.error('invalid bug report number') sys.exit(1) print 'Logging into Launchpad... You have to allow "Change anything" privileges.' crashdb = CrashDatabase(None, None, {'distro': 'ubuntu'}) print 'Downloading bug information...' try: bug = crashdb.launchpad.bugs[int(bug_number)] except KeyError: print 'The bug number %s does not exist in Launchpad.' % bug_number sys.exit(1) if bug.owner.name != crashdb.launchpad.me.name: print 'You are not the reporter of bug %s.' % bug_number while True: val = raw_input('Is that really the bug you want to update? [y/N]') if val.lower() in ('y', 'yes'): break elif val.lower() in ('n', 'no', ''): sys.exit(1) else: print 'Invalid answer.' report = apport.Report('Bug') print 'Bug title:', bug.title info_collected = False if opts.package: collect(report, opts.package) info_collected = True else: # determine bug tasks and collect for those for task in bug.bug_tasks: match = bug_target_re.search(task.target.self_link) if not match: print 'Ignoring task', task.target continue if task.status in ('Invalid', "Won't Fix", 'Fix Released'): print 'Ignoring task %s because it is closed' % task.target continue src = match.group('source') report['SourcePackage'] = src report['Package'] = src # no way to find this out # we either must have the package installed or a source package hook # available to collect sensible information try: apport.packaging.get_version(report['Package']) info_collected = True except ValueError: if os.path.exists('/usr/share/apport/package-hooks/source_%s.py' % report['SourcePackage']): info_collected = True else: print 'Package %s not installed and no hook available, ignoring' % src continue collect(report, src) report.add_os_info() report.add_user_info() report.add_proc_environ() if not info_collected: print 'No additional information collected.' sys.exit(0) # delete the uninteresting keys del report['ProblemType'] del report['Date'] try: del report['SourcePackage'] except KeyError: pass try: upload(report, bug) bug = crashdb.launchpad.bugs[int(bug_number)] # LP#336866 workaround x = bug.tags[:] # LP#254901 workaround x.append('apport-collected') bug.tags = x bug.lp_save() except HTTPError, e: print >> sys.stderr, 'Error connecting to Launchpad: %s\nYou have to allow "Change anything" privileges.\nYou can reset the credentials by removing the file "~/.cache/apport/launchpad.credentials."' % str(e) sys.exit(1)